﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// A class for handling HDR and LDR images.
    /// </summary>
    public sealed class HDRFilter
    {

        private const double MIN_GAMMA = 0.2;
        private const double MAX_GAMMA = 5.0;

        /// <summary>
        /// Convert several 24bpp LDR images into a single 48bpp HDR image.
        /// TODO: weighted averaging
        /// </summary>
        public float[][,] CreateHDR(string[] paths)
        {

            int n = paths.Length;
            int i = 0;

            Bitmap[] bmp = new Bitmap[n];
            BitmapData[] bd = new BitmapData[n];

            for (i = 0; i < n; i++)
            {

                bmp[i] = (Bitmap)Image.FromFile(paths[i]);
                bd[i] = bmp[i].LockBits(new Rectangle(0, 0, bmp[0].Width, bmp[0].Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            }

            float[][,] map = new float[3][,];

            for (i = 0; i < 3; i++)
                map[i] = new float[bd[0].Width, bd[0].Height];

            int x = 0, y = 0;
            int nOffset = (bd[0].Stride - bd[0].Width * 3);

            unsafe
            {

                for (i = 0; i < n; i++)
                {

                    byte* p = (byte*)bd[i].Scan0;

                    for (y = 0; y < bd[i].Height; y++)
                    {
                        for (x = 0; x < bd[i].Width; x++)
                        {

                            map[2][x, y] += p[2];
                            map[1][x, y] += p[1];
                            map[0][x, y] += p[0];

                            p += 3;
                        }

                        p += nOffset;
                    }
                }

                for (y = 0; y < bd[0].Height; y++)
                {
                    for (x = 0; x < bd[0].Width; x++)
                    {

                        map[2][x, y] = ((float)map[2][x, y] / (255 * n));
                        map[1][x, y] = ((float)map[1][x, y] / (255 * n));
                        map[0][x, y] = ((float)map[0][x, y] / (255 * n));
                    }
                }
            }

            for (i = 0; i < n; i++)
            {

                bmp[i].UnlockBits(bd[i]);
                bmp[i].Dispose();
            }

            return map;
        }

        /// <summary>
        /// Load 48bpp HDR image from file.
        /// </summary>
        public float[][,] LoadHDR(string path)
        {
            //uint dib = FreeImage.Load(FreeImage.GetFIFFromFilename(path), path, 0);

            //FREE_IMAGE_COLOR_TYPE colorType = FreeImage.GetColorType(dib);

            //if (colorType.Equals(FREE_IMAGE_COLOR_TYPE.FIC_RGB) == false)
            //    return null;

            //uint bpp = FreeImage.GetBPP(dib);

            //if (bpp != 24 && bpp != 48 && bpp != 96)
            //    return null;

            //int width = (int)FreeImage.GetWidth(dib);
            //int height = (int)FreeImage.GetHeight(dib);
            //int x = 0, y = 0;

            //float[][,] map = new float[3][,];

            //for (int i = 0; i < 3; i++)
            //    map[i] = new float[width, height];

            //if (bpp == 24)
            //{

            //    #region convert 24RGB

            //    unsafe
            //    {

            //        byte* pIn = (byte*)IntPtr.Zero;

            //        for (y = 0; y < height; y++)
            //        {

            //            pIn = (byte*)FreeImage.GetScanLine(dib, y);

            //            for (x = 0; x < width; x++)
            //            {

            //                map[2][x, y] = (float)(pIn[2] / 255.0);
            //                map[1][x, y] = (float)(pIn[1] / 255.0);
            //                map[0][x, y] = (float)(pIn[0] / 255.0);

            //                pIn += 3;
            //            }
            //        }
            //    }

            //    #endregion
            //}
            //else if (bpp == 48)
            //{

            //    #region convert 48RGB

            //    int R = 0, G = 0, B = 0;

            //    unsafe
            //    {

            //        byte* pIn = (byte*)IntPtr.Zero;

            //        for (y = 0; y < height; y++)
            //        {

            //            pIn = (byte*)FreeImage.GetScanLine(dib, y);

            //            for (x = 0; x < width; x++)
            //            {

            //                R = ((pIn[1] << 8) + pIn[0]);
            //                G = ((pIn[3] << 8) + pIn[2]);
            //                B = ((pIn[5] << 8) + pIn[4]);

            //                map[2][x, y] = (float)(R / 65535.0);
            //                map[1][x, y] = (float)(G / 65535.0);
            //                map[0][x, y] = (float)(B / 65535.0);

            //                pIn += 6;
            //            }
            //        }
            //    }

            //    #endregion
            //}
            //else
            //{

            //    #region convert 96RGBF

            //    byte[] bytes = new byte[4];
            //    float R = 0, G = 0, B = 0;

            //    unsafe
            //    {

            //        byte* pIn = (byte*)IntPtr.Zero;

            //        for (y = 0; y < height; y++)
            //        {

            //            pIn = (byte*)FreeImage.GetScanLine(dib, y);

            //            for (x = 0; x < width; x++)
            //            {

            //                bytes[0] = pIn[0];
            //                bytes[1] = pIn[1];
            //                bytes[2] = pIn[2];
            //                bytes[3] = pIn[3];

            //                R = BitConverter.ToSingle(bytes, 0);

            //                bytes[0] = pIn[4];
            //                bytes[1] = pIn[5];
            //                bytes[2] = pIn[6];
            //                bytes[3] = pIn[7];

            //                G = BitConverter.ToSingle(bytes, 0);

            //                bytes[0] = pIn[8];
            //                bytes[1] = pIn[9];
            //                bytes[2] = pIn[10];
            //                bytes[3] = pIn[11];

            //                B = BitConverter.ToSingle(bytes, 0);

            //                map[2][x, y] = R;
            //                map[1][x, y] = G;
            //                map[0][x, y] = B;

            //                pIn += 12;
            //            }
            //        }
            //    }

            //    #endregion
            //}

            //FreeImage.Unload(dib);

            //return map;
            return null;
        }

        /// <summary>
        /// Apply gamma correction to a given HDR image.
        /// </summary>
        public void ApplyGamma(float[][,] map, double gamma)
        {

            int width = map[0].GetLength(0);
            int height = map[0].GetLength(1);

            double exp = (1.0 / Math.Min(Math.Max(gamma, MIN_GAMMA), MAX_GAMMA));
            int x = 0, y = 0;

            for (int c = 0; c < 3; c++)
                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                        map[c][x, y] = (float)Math.Pow(map[c][x, y], exp);
        }

        /// <summary>
        /// Save HDR image to file.
        /// </summary>
        //public void Save(float[][,] map, string path, bool hdr)
        //{

        //    int width = map[0].GetLength(0);
        //    int height = map[0].GetLength(1);
        //    int x = 0, y = 0;

        //    FREE_IMAGE_TYPE type = (hdr ? FREE_IMAGE_TYPE.FIT_RGB16 : FREE_IMAGE_TYPE.FIT_BITMAP);
        //    int bpp = (hdr ? 48 : 24);

        //    uint dib = FreeImage.AllocateT(type, width, height, bpp, 0, 0, 0);

        //    unsafe
        //    {

        //        byte* p = (byte*)IntPtr.Zero;

        //        if (hdr)
        //        {

        //            #region write 48 bit pixel values

        //            int R = 0, G = 0, B = 0;

        //            for (y = 0; y < height; y++)
        //            {

        //                p = (byte*)FreeImage.GetScanLine(dib, height - 1 - y);

        //                for (x = 0; x < width; x++)
        //                {

        //                    R = (int)Math.Round(map[2][x, y] * 65535);
        //                    G = (int)Math.Round(map[1][x, y] * 65535);
        //                    B = (int)Math.Round(map[0][x, y] * 65535);

        //                    p[5] = (byte)(B >> 8);
        //                    p[4] = (byte)(B - ((B >> 8) << 8));
        //                    p[3] = (byte)(G >> 8);
        //                    p[2] = (byte)(G - ((G >> 8) << 8));
        //                    p[1] = (byte)(R >> 8);
        //                    p[0] = (byte)(R - ((R >> 8) << 8));

        //                    p += 6;
        //                }
        //            }

        //            #endregion
        //        }
        //        else
        //        {

        //            #region write 24 bit pixel values

        //            for (y = 0; y < height; y++)
        //            {

        //                p = (byte*)FreeImage.GetScanLine(dib, y);

        //                for (x = 0; x < width; x++)
        //                {

        //                    p[2] = (byte)Math.Round(map[2][x, y] * 255);
        //                    p[1] = (byte)Math.Round(map[1][x, y] * 255);
        //                    p[0] = (byte)Math.Round(map[0][x, y] * 255);

        //                    p += 3;
        //                }
        //            }

        //            #endregion
        //        }
        //    }

        //    FREE_IMAGE_FORMAT fif = FreeImage.GetFIFFromFilename(path);

        //    FreeImage.Save(fif, dib, path, 0);
        //    FreeImage.Unload(dib);
        //}
    }
}
